/*
 * A simple CORBA banking application
 * Part of the GICOM pedagogical project
 */
package gicom.bank;

import gicom.GenericManager;
import gicom.PersistentObject;
import gicom.config.ObjectConfig;
import gicom.generated.bankServices.Account;
import gicom.generated.bankServices.AccountHelper;
import gicom.generated.bankServices.Address;
import gicom.generated.bankServices.Branch;
import gicom.generated.bankServices.BranchHelper;
import gicom.generated.bankServices.CustomerHelper;
import gicom.generated.bankServices.CustomerOperations;
import gicom.generated.bankServices.CustomerPOATie;
import gicom.generated.bankServices.Gender;
import gicom.generated.bankServices.ProblemException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.Servant;

/**
 * The transient Customer implementation
 */
public class CustomerImpl extends PersistentObject implements CustomerOperations {
    
    /**
     * The customer's number
     */
    private String number = null;
    
    /**
     * The customer's address
     */
    private Address addr = null;
    
    /**
     * The customer's name
     */
    private String name = null;
    
    /**
     * The customer's gender
     */
    private Gender gender = null;
    
    /**
     * The branch reference
     */
    private transient Branch branch = null;
    
    /**
     * The branch name
     */
    private String branchName = null;
    
    /**
     * The customers' accounts
     */
    private transient Map<String, Account> accounts = null;
    
    /**
     * The customers' accounts implementations
     */
    private transient Map<String, AccountImpl> accountImpls = null;
    
    private static Logger logger = Logger.getLogger(BankImpl.class.getName());
    
    /**
     * @param number
     *            the customer's number
     * @param name
     *            the customer's name
     * @param gender
     *            the customer's gender
     * @param addr
     *            the customer's address
     * @param branch
     *            the customer's branch
     */
    public CustomerImpl(String number, String name, Gender gender, Address addr, Branch branch) {
        super(number);
        this.number = number;
        this.name = name;
        this.gender = gender;
        this.addr = addr;
        this.branch = branch;
        this.branchName = branch.name();
        this.accounts = new HashMap<String, Account>();
        this.accountImpls = new HashMap<String, AccountImpl>();
        
    }
    
    public CustomerImpl() {
        
    }
    
    /**
     * Get the customer's address
     *
     * @return the customer's address
     */
    public Address addr() {
        return this.addr;
    }
    
    /**
     * Set the customer's address
     *
     * @param arg
     *            the customer's address
     */
    public void addr(Address arg) {
        addr = arg;
    }
    
    /**
     * Create an account
     *
     * @param balance
     *            the account balance
     * @return the account reference
     * @throws bankServices.ProblemException
     *             This exception is raised when a problem occurs in the banking
     *             business methods
     */
    public Account createAccount(String number, float balance) throws ProblemException {
        try {
            AccountImpl accountImpl = new AccountImpl(number, balance, CustomerHelper.narrow(getReference()));
            ObjectConfig config = new ObjectConfig(number, getPOA().the_name(), CustomerImpl.class.getName(), null, null, null, accountImpl);
            this.createObject(config);

            // Create the account reference
            Account account = AccountHelper.narrow(accountImpl.getReference());
            
            // Put the reference of the account in the vector
            accounts.put(number, account);
            accountImpls.put(number, accountImpl);

            logger.info("create account " + number);
            this.save();
            
            return account;
        } catch (Exception ex) {
            throw new ProblemException("Error while creating the account", ex.getMessage());
        }
    }
    
    
    /**
     * Remove an account
     *
     * @param accountnumber
     *            the account number
     * @throws bankServices.ProblemException
     *             This exception is raised when a problem occurs in the banking
     *             business methods
     */
    public void removeAccount(String number) throws ProblemException {
        try {
            if (accounts.containsKey(number)) {
                try {
                    accounts.remove(number);
                    AccountImpl accountImpl = accountImpls.remove(number);
                    accountImpl.destroy();
                } catch (Exception ex) {
                    throw new ProblemException("error while destroying the account", ex.getMessage());
                }
            } else {
                throw new ProblemException("Account number error", "The account number doesn't exist");
            }
            this.save();
        } catch (Exception ex) {
            throw new ProblemException("Error while removing the account", ex.getMessage());
        }
    }
    
    /**
     * Find a specific account by its number
     *
     * @param accountnumber
     *            the account number
     * @return a specific account
     * @throws bankServices.ProblemException
     *             This exception is raised when a problem occurs in the banking
     *             business methods
     */
    public Account findAccountByPrimaryKey(String number)
    throws ProblemException {
        
        if (accounts.containsKey(number)){
            return (Account) accounts.get(number);
        } else
            throw new ProblemException("Account number error", "This account number doesn't exist");
    }
    
    /**
     * Find all accounts for this customer
     *
     * @return all accounts for this customer
     * @throws bankServices.ProblemException
     *             This exception is raised when a problem occurs in the banking
     *             business methods
     */
    public Account[] findAllAccounts() throws ProblemException {
        return accounts.values().toArray(new Account[accounts.size()]);
    }
    
    /**
     * Get the customer's gender
     *
     * @return the customer's name
     */
    public Gender gend() {
        return this.gender;
    }
    
    /**
     * Get the customer's name
     *
     * @return the customer's name
     */
    public String name() {
        return this.name;
    }
    
    /**
     * Get the customer's number
     *
     * @return the customer's number
     */
    public String number() {
        return this.number;
    }
    
    /**
     * Get the branch reference
     *
     * @return the branch reference
     */
    public Branch refBranch() {
        return branch;
    }
    
    public Account getAccountReference(String number) {
        return accounts.get(number);
    }
    
    public Servant createServant() {
        return new CustomerPOATie(this);
    }

    /*
     * Persistence
     */
    
    private transient String refBranch;
    private transient List<String> accs;
    
    @Override
    public void doReload(ORB orb, POA poa) throws Exception {
	// branch
	this.branch = BranchHelper.narrow(orb.string_to_object(this.refBranch));
	// accounts
	this.accounts = new HashMap<String, Account>();
        this.accountImpls = new HashMap<String, AccountImpl>();
	
	Iterator<String> iter = accs.iterator();
	while (iter.hasNext()) {
	    String string = (String) iter.next();
	    org.omg.CORBA.Object o = orb.string_to_object(string);
	    Account a = AccountHelper.narrow(o);
	    this.accounts.put(a.number(), a);
	}
    }
   
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
	
        accs = new ArrayList<String>();
        // id
        this.id = in.readUTF();
	
	// number
	this.number = in.readUTF();
	
	// addr
	this.addr = (Address)in.readObject();
	
	// name
	this.name = in.readUTF();
	
	// gender
	this.gender = (Gender) in.readObject();
	
	// branch
	this.refBranch = in.readUTF();
	
	// accounts
	int count = in.readInt();
	for (int i = 0 ; i<count ; i++){
	    this.accs.add(in.readUTF());
	}
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        try {

            // id
            out.writeUTF(this.id);

            // number
            out.writeUTF(this.number);

            
            out.writeObject(this.addr);

            // name
            out.writeUTF(this.name);

            // gender
            out.writeObject(this.gender);

            // branch
            out.writeUTF(getORB().object_to_string(this.branch));

            // accounts
            out.writeInt(this.accounts.size());
            for (Account a : this.accounts.values()) {
                String s = getORB().object_to_string(a);
                out.writeUTF(s);
            }
        } catch (Exception ex) {
            throw new IOException("Error while writing the customer", ex);
        }
    }
    
}
